/*
 * Copyright (c) 2009, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tssp_internal.h"
#include "dvbpsi/dvbpsi.h"
#include "dvbpsi/psi.h"
#include "dvbpsi/demux.h"

struct tssp_section_listener {
    struct tssp *parser;
    struct tssp_section_filter filter;
    tssp_section_callback callback;
    void *userdata;
    struct tssp_timer_handle *timer;
    dvbpsi_handle demux_handle;

    STAILQ_ENTRY(tssp_section_listener) entry;
    STAILQ_ENTRY(tssp_section_listener) del_entry; /* TODO: remove this */
    int removed;
};

struct tssp_dvbpsi {
    uint8_t is_cb;
    struct tssp_section_listeners removed_listeners; /* TODO: remove this */
};

static void 
tssp_dvbpsi_section_destroy(struct tssp_section *section)
{
    dvbpsi_DeletePSISections(section->impl.instance);
}

static void
tssp_dvbpsi_section_filter_handle_destroy(struct tssp *parser,
                                          struct tssp_section_listener *listener)
{
    if (listener->timer && parser->timer_delete) {
        parser->timer_delete(listener->timer);
        listener->timer = NULL;
    }

    if (listener->demux_handle) {
        dvbpsi_DetachDemux(listener->demux_handle);
        listener->demux_handle = NULL;
    }

    free(listener);
}

static struct tssp_section *
tssp_dvbpsi_section_create(dvbpsi_psi_section_t *psi_section)
{
    struct tssp_section *section;
    struct tssp_section *current;
    dvbpsi_psi_section_t *tmp = psi_section->p_next;

    section = malloc(sizeof(*section));
    if (section == NULL) {
        TSSP_TRACE("MEMORY ALLOCATION FAILED");
        return NULL;
    }

    section->table_id = psi_section->i_table_id;
    section->syntax_indicator = psi_section->b_syntax_indicator;
    section->private_indicator = psi_section->b_private_indicator;
    section->length = psi_section->i_length;

    /* data pointers */
    section->data.raw = psi_section->p_data;
    section->data.start = psi_section->p_payload_start;
    section->data.end = psi_section->p_payload_end;

    /* impl specific */
    section->impl.instance = psi_section;
    section->impl.destroy = tssp_dvbpsi_section_destroy;

    if (section->syntax_indicator) {
        section->extension = psi_section->i_extension;
        section->version = psi_section->i_version;
        section->current_next = psi_section->b_current_next;
        section->number = psi_section->i_number;
        section->last_number = psi_section->i_last_number;
        section->next_section = NULL;
        current = section;
        while (tmp) {
            current->next_section = tssp_dvbpsi_section_create(tmp);
            if (current->next_section == NULL) {
                tssp_section_destroy(section);
                return NULL;
            }
            current = current->next_section;
            tmp = tmp->p_next;
        }
    } else {
        section->extension = 0;
        section->version = 0;
        section->current_next = 0;
        section->number = 0;
        section->last_number = 0;
        section->data.raw = NULL;
        section->data.start = NULL;
        section->data.end = NULL;
        section->next_section = NULL;
    }

    return section;
}

static
void section_filter_handler(dvbpsi_handle handle,
                            dvbpsi_psi_section_t *psi_section)
{
    int i;
    int x;
    int stop;
    int matched = 1;
    struct tssp_section_listener *listener;
    struct tssp_section_filter *f;
    struct tssp_section *section;
    struct tssp_dvbpsi *instance;

    TSSP_ASSERT(psi_section);

    listener = ((dvbpsi_demux_t *)handle->p_private_decoder)->p_new_cb_data;
    instance = listener->parser->impl.instance;
    f = &listener->filter;
#if 0
    stop = psi_section->b_syntax_indicator ? sizeof(listener->filter.mask) : 3;
#else
    stop = sizeof(listener->filter.mask);
#endif

    do {
        if (stop > psi_section->i_length) {
            TSSP_LOG("section length too short: %d\n", psi_section->i_length);
            break;
        }

        for (i = 0; i < stop; i++) {
            x = (i == 0) ? (i) : (i + 2);
            if ((psi_section->p_data[x] & f->mask[i]) != (f->value[i] & f->mask[i])) {
                matched = 0;
                break;
            }
        }

        if (matched == 0)
            break;

        section = tssp_dvbpsi_section_create(psi_section);
        if (section == NULL)
            break;

        /* cancel timer */
        if (listener->timer && listener->parser->timer_delete) {
            listener->parser->timer_delete(listener->timer);
            listener->timer = NULL;
        }

        instance->is_cb = 1;
        listener->callback(TSSP_STATUS_OK, listener, section, listener->userdata);
        instance->is_cb = 0;
        return;

    } while (0);

    dvbpsi_DeletePSISections(psi_section);
}

static
void filter_timeout_callback(void *userdata)
{
    struct tssp_section_listener *listener = userdata;

    /* stop demux */
    if (listener->demux_handle) {
        dvbpsi_DetachDemux(listener->demux_handle);
        listener->demux_handle = NULL;
    }

    if (listener->parser->timer_delete)
        listener->parser->timer_delete(listener->timer);
    listener->timer = NULL;

    listener->callback(TSSP_STATUS_TIMEOUT, listener, NULL, listener->userdata);
}


static
struct tssp_section_listener *
tssp_dvbpsi_add_section_filter(struct tssp *parser,
                               const struct tssp_section_filter *filter,
                               tssp_section_callback callback,
                               void *userdata)
{
    struct tssp_section_listener *listener;

    TSSP_ASSERT(parser);

    if (filter == NULL || callback == NULL)
        return NULL;

    listener = malloc(sizeof(*listener));
    if (listener == NULL)
        return NULL;

    /* allocate demux resource */
    listener->demux_handle = dvbpsi_AttachDemux(NULL, listener);
    if (listener->demux_handle == NULL) {
        free(listener);
        return NULL;
    }
    /* might be a little hacky */
    listener->demux_handle->pf_callback = section_filter_handler;

    /* copy parameters */
    listener->filter.pid = filter->pid;
    memcpy(listener->filter.mask, filter->mask, sizeof(filter->mask));
    memcpy(listener->filter.value, filter->value, sizeof(filter->value));
    listener->filter.timeout = filter->timeout;
    listener->callback = callback;
    listener->userdata = userdata;
    listener->parser = parser;
    listener->timer = NULL;
    listener->removed = 0;

    /* timeout operation */
    if (parser->timer_create && filter->timeout) {
        listener->timer = parser->timer_create(filter->timeout,
                                               filter_timeout_callback,
                                               listener);
        if (listener->timer == NULL) {
            tssp_dvbpsi_section_filter_handle_destroy(parser, listener);
            return NULL;
        }
    }

    STAILQ_INSERT_TAIL(&parser->section_listeners, listener, entry);

    return listener;
}

static
void
tssp_dvbpsi_remove_section_filter(struct tssp *parser,
                                  struct tssp_section_listener *listener)
{
    struct tssp_section_listener *tmp;
    struct tssp_dvbpsi *instance = parser->impl.instance;

    TSSP_ASSERT(parser);

    if (instance->is_cb) {
        listener->removed = 1;
        STAILQ_INSERT_TAIL(&instance->removed_listeners, listener, del_entry);
        return;
    }

    STAILQ_FOREACH(tmp, &parser->section_listeners, entry) {
        if (tmp == listener) {
            STAILQ_REMOVE(&parser->section_listeners, listener,
                          tssp_section_listener, entry);
            tssp_dvbpsi_section_filter_handle_destroy(parser, listener);
            return;
        }
    }
}

static void tssp_dvbpsi_flush_listeners(struct tssp *parser)
{
    struct tssp_section_listener *tmp;
    struct tssp_dvbpsi *instance = parser->impl.instance;

    TSSP_ASSERT(instance->is_cb == 0);

    while ((tmp = STAILQ_FIRST(&instance->removed_listeners))) {
        STAILQ_REMOVE_HEAD(&instance->removed_listeners, del_entry);
        tssp_dvbpsi_remove_section_filter(parser, tmp);
    }
}

static
void tssp_dvbpsi_push_packet(struct tssp *parser, const uint8_t *packet)
{
    uint16_t pid;
    struct tssp_section_listener *listener;

    TSSP_ASSERT(parser);
    TSSP_ASSERT(packet);
    TSSP_ASSERT(packet[0] == 0x47);

    pid = ((uint16_t)(packet[1] & 0x1F) << 8) + packet[2];

    STAILQ_FOREACH(listener, &parser->section_listeners, entry) {
        if (listener->removed == 0)
            if (pid == listener->filter.pid && listener->demux_handle)
                dvbpsi_PushPacket(listener->demux_handle, (uint8_t *)packet);
    }
    tssp_dvbpsi_flush_listeners(parser);
}

static
void tssp_dvbpsi_destroy(struct tssp *parser)
{
    struct tssp_section_listener *listener;

    if (parser == NULL)
        return;

    /* clear filters */
    while ((listener = STAILQ_FIRST(&parser->section_listeners)))
        tssp_dvbpsi_remove_section_filter(parser, listener);

    free(parser->impl.instance);
}

int tssp_impl_init(struct tssp *parser)
{
    struct tssp_dvbpsi *instance;

    TSSP_ASSERT(parser);

    instance = malloc(sizeof(*instance));
    if (instance == NULL)
        return TSSP_STATUS_MEMORY_ERROR;
    instance->is_cb = 0;
    STAILQ_INIT(&instance->removed_listeners);

    parser->impl.instance = instance;
    parser->impl.add_section_filter = tssp_dvbpsi_add_section_filter;
    parser->impl.remove_section_filter = tssp_dvbpsi_remove_section_filter;
    parser->impl.push_packet = tssp_dvbpsi_push_packet;
    parser->impl.destroy = tssp_dvbpsi_destroy;

    return TSSP_STATUS_OK;
}

